//
//  GitTfAdapter.swift
//  GitTFGUI
//
//  Created by Samuel Richard Magny on 2015-08-29.
//  Copyright (c) 2015 Samuel Richard Magny. All rights reserved.
//

import Cocoa
import SwiftTools

internal class GitTfAdapter : ClientAdapter {

    private var commandLineAppLauncher:CommandLineAppLauncher?
    private var _currentCommand:TFSCommandType = TFSCommandType.None
    
    private var _configDict:Dictionary<String, Dictionary<String, String>>
    
    
    
    private let bundleIdentifier = "Samuel-Richard-Magny.TFSClient"
    
    private let resourcesFolderPath = "/Resources/git-tf/"
    private let executableFileName = "git-tf"
    
    private let gitTfApplicationLaunchPath:String
    
    private static let gitDirectoryRelativePath:String = "/.git"
    private static let gitTFSFileRelativePath:String = "/.git" + "/git-tf" //Strangely, swift doesn't allow me to combine with : "let gitDirectoryRelativePath"
    private static let gitConfigFileRelativePath:String = "/.git" + "/config"
    
    private let gitTfPrefixString = "git-tf: "
    
    
    
    override var workingDirectory: String! {
        didSet(newValue)
        {
            parseWorkingDirectoryConfigFile()
        }
    }
    
    internal var delegate:GitTfAdapterDelegate?
    
    internal init(workingDirectory:String, delegate:GitTfAdapterDelegate? = nil) {
        self.delegate = delegate
        
        _configDict = GitTFConfigFileParser.ParseFile(workingDirectory + GitTfAdapter.gitConfigFileRelativePath);
        
        //Get executable resource
        let bundle:NSBundle  = NSBundle(identifier: bundleIdentifier)!
        let resourcePath = resourcesFolderPath + executableFileName;
        gitTfApplicationLaunchPath = bundle.pathForResource(resourcePath, ofType: "")!

        
        super.init(workingDirectory: workingDirectory)
    }
    
    internal func checkin() {
        self.launchCommand(TFSCommandType.Checkin)
    }
    
    internal func pull() {
        self.launchCommand(TFSCommandType.Pull)
    }
    
    internal func fetch() {
        self.launchCommand(TFSCommandType.Fetch)
    }

    internal func configure(localDirectoryPath:String, serverUrl: String, serverProjectPath: String) {
        
        prepareDirectoryForConfigure(localDirectoryPath)
        self.workingDirectory = localDirectoryPath;
        
        let serverHttpUrl = formatServerUrl(serverUrl)
        let serverProjectPath = formatServerProjectPath(serverProjectPath)
        self.launchCommand(TFSCommandType.Configure, arguments: [serverHttpUrl, serverProjectPath])
    }
    
    internal func clone(localDirectoryPath:String, serverUrl: String, serverProjectPath: String) {
        
        prepareDirectoryForCloning(localDirectoryPath)
        self.workingDirectory = localDirectoryPath;

        let serverHttpUrl = formatServerUrl(serverUrl)
        let serverProjectPath = formatServerProjectPath(serverProjectPath)
        self.launchCommand(TFSCommandType.Clone, arguments: [serverHttpUrl, serverProjectPath])
    }
    
    internal func getWorkingDirectoryServerUrl()->String
    {
        if var gitTfServerConfigDict:Dictionary<String, String> = self._configDict[GitTFConfigFileParser.gitTfServerConfigKey]
        {
            if let serverUrl = gitTfServerConfigDict[GitTFConfigFileParser.gitTfServer_CollectionConfigKey]
            {
                return serverUrl as String;
            }
        }

        return ""
    }
    
    internal func getWorkingDirectoryServerPath()->String
    {
        if var gitTfServerConfigDict:Dictionary<String, String> = self._configDict[GitTFConfigFileParser.gitTfServerConfigKey]
        {
            if let serverUrl = gitTfServerConfigDict[GitTFConfigFileParser.gitTfServer_ServerPathConfigKey]
            {
                return serverUrl as String;
            }
        }
        
        return ""
    }
    
    private func prepareDirectoryForConfigure(localDirectoryPath:String) -> Bool
    {
        let fileManager = NSFileManager.defaultManager()
        if (!fileManager.fileExistsAtPath(localDirectoryPath))
        {
            do {
                try fileManager.createDirectoryAtPath(localDirectoryPath, withIntermediateDirectories: true, attributes: nil)
            }catch _ as NSError {
                return false
            }
        }
        
        return true
    }
    
    private func formatServerUrl(serverUrl:String)->String {
        var serverHttpUrl: String  = serverUrl
        if(!serverHttpUrl.hasPrefix("http://") && !serverHttpUrl.hasPrefix("https://")) {
            serverHttpUrl = "https://" + serverHttpUrl
        }
        
        return serverHttpUrl
    }
    
    private func formatServerProjectPath(path:String)->String {
        var repoPath: String = path
        if(repoPath.isEmpty) {
            repoPath = "$/"
        }

        return repoPath
    }
    
    private func prepareDirectoryForCloning(localDirectoryPath:String) -> Bool {
        let retVal = prepareDirectoryForConfigure(localDirectoryPath)
        
        let fileManager = NSFileManager.defaultManager()
        if(retVal && fileManager.fileExistsAtPath(localDirectoryPath))
        {
            do {
                try fileManager.removeItemAtPath(self.workingDirectory + "/.DS_Store")
                try fileManager.removeItemAtPath(self.workingDirectory + "/.git")
            }
            catch _
            {}
        }
        
        return retVal
    }
    
    internal static func isWorkingDirectoryValid(workingDirectoryPath:String)->Bool {
        let fileManager = NSFileManager.defaultManager();
        
        if(!fileManager.fileExistsAtPath(workingDirectoryPath + gitTFSFileRelativePath)) {
            return false;
        }
        
        return true;
    }
    

    
    //Privates
    //-------------------------------------------------------------------------
    private func launchCommand(command:TFSCommandType, arguments: Array<String> = []) {
        
        commandLineAppLauncher = CommandLineAppLauncher(applicationPath: gitTfApplicationLaunchPath, workingDirectory: workingDirectory, outputBlock: OnReceiveOutput, errorBlock: OnReceiveErrorOutput)
        
        commandLineAppLauncher?.args.append(command.commandString)
        commandLineAppLauncher?.args.appendContentsOf(arguments)

        _currentCommand = command
        
        commandLineAppLauncher?.launch(OnTermination)
        
        if let delegate = self.delegate {
            delegate.commandDidStart(_currentCommand)
        }
    }
    
    private func OnTermination(withSuccess:Bool)
    {
        if(withSuccess && workingDirectory.isEmpty)
        {
            self.parseWorkingDirectoryConfigFile()
        }
        
        if let delegate = self.delegate
        {
            delegate.commandDidStop(withSuccess, command: _currentCommand)
        }
        
        _currentCommand = TFSCommandType.None
        
    }
    
    private func OnReceiveOutput(dataString:String) -> Bool
    {
        if(dataString != "\n") {
            let instruction = parseTFSInstruction(dataString)
            handleTFSInstruction(instruction)
        }
        
        return true
    }
    
    private func OnReceiveErrorOutput(dataString:String) -> Bool
    {
        if(dataString.hasPrefix("Warning") || dataString == "\n"){
            return true
        }
        
        notifyErrorOccured(_currentCommand, errorReason: dataString)
        
        return true
    }
    
    private func writeInput(input:String)
    {
        commandLineAppLauncher?.write(input)
    }
    
    private func parseTFSInstruction(rawInstruction:String) -> TFSInstructionsType
    {
        if (rawInstruction == TFSInstructionsType.SetUsername.instructionID)
        {
            return TFSInstructionsType.SetUsername
        }
        else if (rawInstruction == TFSInstructionsType.SetPassword.instructionID)
        {
            return TFSInstructionsType.SetPassword
        }
        else if (rawInstruction.hasPrefix(TFSInstructionsType.ConnectingToTFSServer.instructionID))
        {
            return TFSInstructionsType.ConnectingToTFSServer
        }
        else if (rawInstruction.hasPrefix(TFSInstructionsType.CloningInto.instructionID))
        {
            return TFSInstructionsType.CloningInto
        }
        else if (rawInstruction.hasPrefix(TFSInstructionsType.CheckinInto.instructionID))
        {
            return TFSInstructionsType.CheckinInto
        }
        else if (rawInstruction.hasPrefix(TFSInstructionsType.NotingToCheckin.instructionID))
        {
            return TFSInstructionsType.NotingToCheckin
        }
        else if (rawInstruction.hasPrefix(TFSInstructionsType.FetchingAndMerging.instructionID))
        {
            return TFSInstructionsType.FetchingAndMerging
        }
        else if (rawInstruction.hasPrefix(TFSInstructionsType.Done.instructionID))
        {
            return TFSInstructionsType.Done
        }
        
        return TFSInstructionsType.Unknown
    }
    
    private func handleTFSInstruction(instruction: TFSInstructionsType) {
        
        switch(instruction)
        {
        case TFSInstructionsType.SetUsername:
            writeInput(self.delegate!.getUsername())
            break
        case TFSInstructionsType.SetPassword:
            writeInput(self.delegate!.getPassword())
            break
        case TFSInstructionsType.ConnectingToTFSServer:
            
            break
        case TFSInstructionsType.CloningInto:
            break
        case TFSInstructionsType.CheckinInto:
            break
        case TFSInstructionsType.NotingToCheckin:
            break
        case TFSInstructionsType.FetchingAndMerging:
            break
        case TFSInstructionsType.Done:
            break
        default:
            break
        }
        
    }
    
    private func notifyErrorOccured(command:TFSCommandType, errorReason:String)
    {
        if let delegate = self.delegate
        {
            delegate.errorOccured(command, errorReason: errorReason)
        }
    }

    private func notifyProgressInfo(info:String)
    {
        if let delegate = self.delegate
        {
            delegate.progressInfo(info)
        }
    }

    private func parseWorkingDirectoryConfigFile()
    {
        _configDict = GitTFConfigFileParser.ParseFile(self.workingDirectory + GitTfAdapter.gitConfigFileRelativePath);
        
    }
    
}
